package com.googlecode.ddplan;

import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.armedbear.lisp.Interpreter;
import org.armedbear.lisp.LispObject;

public class StateAction {
	private static String STATE_REGEX = "[\\!]?(\\$[\\d]+\\.)?\\w+(\\((\\$[\\d]+(,\\$[\\d]+)*)?\\))?";
	private Method action;
	private String state;

	private boolean not;
	private String stateMethodName;
	private List<String> parameters;

	protected StateAction(String state, Method action) {
		this.state = state.trim().replaceAll(" ", "");
		this.action = action;
		parameters = new ArrayList<String>();
	}

	protected void parse() throws ParseException {
		if (!state.matches(STATE_REGEX)) {
			throw new ParseException("Cannot parse state :" + state + " for action method " + action.getClass().getName() + "."
					+ action.getName(), 0);
		}

		not = state.startsWith("!");

		String plainState = state.replaceAll("!", "");

		boolean dotted = plainState.contains(".");
		boolean parenths = plainState.contains("(");
		int dotIndex = plainState.indexOf(".");
		int parenthIndex = plainState.indexOf("(");

		String instance = dotted ? plainState.substring(0, dotIndex) : "$0";
		parameters.add(instance);

		if (!parenths) {
			stateMethodName = dotted ? plainState.split("\\.")[1] : plainState;
		} else {
			stateMethodName = dotted ? plainState.substring(dotIndex + 1, parenthIndex) : plainState.substring(0, parenthIndex);
		}

		if (parenths) {
			parameters.addAll(Arrays.asList(plainState.substring(parenthIndex + 1, plainState.length() - 1).split(",")));
		}
	}

	protected ValidationResult validate() {
		ValidationResult validation = new ValidationResult();
		String errorDesc = "State " + state + " for action " + action.getName();

		try {
			// variables format validations
			for (String parameter : parameters) {
				int variable = Integer.parseInt(parameter.substring(1));
				if (variable > action.getParameterTypes().length) {
					validation.addError(errorDesc + " have an variable number exceeding the method signature");
				}
			}

			Class<?> instanceType = null;
			if (parameters.get(0).equals("$0")) {
				instanceType = action.getDeclaringClass();
			} else {
				int paramNumber = Integer.parseInt(parameters.get(0).substring(1));
				instanceType = action.getParameterTypes()[paramNumber - 1];
			}

			List<Class<?>> parameterTypes = new ArrayList<Class<?>>();
			for (int i = 1; i < parameters.size(); i++) {
				String param = parameters.get(i);
				if (param.equals("$0")) {
					parameterTypes.add(action.getDeclaringClass());
				} else {
					int paramNumber = Integer.parseInt(param.substring(1));
					parameterTypes.add(action.getParameterTypes()[paramNumber - 1]);
				}
			}
			for (Class<?> parameterType : parameterTypes) {
				if (parameterType.getAnnotation(PDDLType.class) == null) {
					validation.addError(errorDesc + " must have all parameters registered as @" + PDDLType.class.getSimpleName());
				}
			}

			Method stateMethod = null;
			for (Method m : instanceType.getMethods()) {
				if (m.getName().equals(stateMethodName) && (m.getAnnotation(PDDLState.class) != null)
						&& Arrays.asList(m.getParameterTypes()).equals(parameterTypes)) {
					stateMethod = m;
					break;
				}
			}
			if (stateMethod == null) {
				validation.addError(errorDesc + " must have a valid @" + PDDLState.class.getSimpleName()
						+ " method with a matching signature");
			}
		} catch (Exception e) {
			validation.addError(e.getMessage());
		}

		return validation;
	}

	protected LispObject getState() {
		LispObject stateLisp = Interpreter.readFromString("NIL");
		stateLisp = stateLisp.push(Interpreter.readFromString(stateMethodName));
		for (String param : parameters) {
			String paramFormatted = param.replaceAll("$", "?");
			stateLisp = stateLisp.reverse().push(Interpreter.readFromString(paramFormatted)).reverse();
		}
		if (not) {
			stateLisp = Interpreter.readFromString("NIL").push(stateLisp).push(Interpreter.readFromString("NOT"));
		}
		return stateLisp;
	}
}
